package com.googlecode.perfectable.persistable.index;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import com.google.common.collect.ImmutableList;
import com.googlecode.perfectable.persistable.index.memory.MemoryIndex;
import com.googlecode.perfectable.persistable.session.Identifier;
import com.googlecode.perfectable.persistable.session.InvalidReference;
import com.googlecode.perfectable.persistable.storage.AbstractStorageDecorator;
import com.googlecode.perfectable.persistable.storage.Storage;

public class Indexer extends AbstractStorageDecorator {
	public Indexer(Storage inner) {
		super(inner);
	}

	private final Map<Function<?, ?>, Index<?, ?>> indexFunctions = new HashMap<>();
	// private Map<Class<?>, Index<?, ?>> indexClasses = new HashMap<>();
	private final PrimaryIndex primaryIndex = new PrimaryIndex();

	public <T, X> Searcher<T, X> search(Function<T, X> property) {
		checkNotNull(property);
		Index<T, X> index = (Index<T, X>) this.indexFunctions.get(property);
		if(index == null) {
			try {
				index = preindex(property);
			}
			catch(InvalidReference e) {
				throw new RuntimeException(e); // TODO Auto-generated catch block
			}
		}
		return new IndexSearcher<>(index);
	}

	@Override
	public <T> Identifier<T> persist(T entity) {
		Identifier<T> result = super.persist(entity);
		reindex(entity, result);
		return result;
	}

	@Override
	public <T> void persist(T entity, Identifier<? super T> identity) {
		super.persist(entity, identity);
		reindex(entity, identity);
	}

	private <T, X> Index<T, X> preindex(Function<T, X> property) throws InvalidReference {
		Index<T, X> newIndex = new MemoryIndex<>(property);
		this.indexFunctions.put(property, newIndex);
		Collection<Identifier<?>> identifiers = this.primaryIndex.scan(null);
		for(Identifier<?> identifier : identifiers)
		{
			Object entity = super.materialize(identifier); // MARK source
			newIndex.insert(entity, identifier);
		}
		return newIndex;
	}

	private <T> void reindex(T entity, Identifier<T> identity) {
		this.primaryIndex.insert(entity, identity);
		for(Index<?, ?> index : this.indexFunctions.values()) {
			index.insert(entity, identity);
		}
	}

	private class IndexSearcher<T, P> implements Searcher<T, P> {

		private final Index<T, P> index;

		public IndexSearcher(Index<T, P> index) {
			this.index = index;
		}

		@Override
		public Collection<Reference<? extends T>> find(P expression) {
			Collection<Identifier<? extends T>> identifiers = this.index.scan(expression);
			ImmutableList.Builder<Reference<? extends T>> resultBuilder = ImmutableList.<Reference<? extends T>> builder();
			for(Identifier<? extends T> identifier : identifiers)
			{
				Reference<? extends T> loadedObject = StandardReference.create(Indexer.this, identifier); // MARK source
				resultBuilder.add(loadedObject);
			}
			return resultBuilder.build();
		}
	}
}
